package com.atguigu.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.entity.*;
import com.atguigu.feign.ProductFeignClient;
import com.atguigu.map.ProductRepository;
import com.atguigu.result.RetVal;
import com.atguigu.search.*;
import com.atguigu.service.SearchIndexService;
import lombok.SneakyThrows;

import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author ：Hachimen
 * @date ：Created in 2021/8/31 16:53
 * @description：es搜索实现类
 * @modified By：
 * @version: 0.0.1
 */
@Service
public class SearchIndexServiceImpl implements SearchIndexService {
    @Autowired
    private ProductRepository productRepository;
    @Autowired
    private ProductFeignClient productFeignClient;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RestHighLevelClient restHighLevelClient;

    @Override
    public void onSale(Long skuId) {
        //赋值
        Product product = new Product();
        //添加
        SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
        if(skuInfo != null){
            product.setId(skuInfo.getId());
            product.setDefaultImage(skuInfo.getSkuDefaultImg());
            product.setProductName(skuInfo.getSkuName());
            product.setPrice(skuInfo.getPrice().doubleValue());
            product.setCreateTime(new Date());
            BaseBrand baseBrand = productFeignClient.query(skuInfo.getBrandId());
            if (baseBrand != null){
                product.setBrandId(baseBrand.getId());
                product.setBrandName(baseBrand.getBrandName());
                product.setBrandLogoUrl(baseBrand.getBrandLogoUrl());
            }
            BaseCategoryView categoryView = productFeignClient.getCategoryView(skuInfo.getCategory3Id());
            if(categoryView != null){
                product.setCategory1Id(categoryView.getCategory1Id());
                product.setCategory1Name(categoryView.getCategory1Name());
                product.setCategory2Id(categoryView.getCategory2Id());
                product.setCategory2Name(categoryView.getCategory2Name());
                product.setCategory3Id(categoryView.getCategory3Id());
                product.setCategory3Name(categoryView.getCategory3Name());
            }
        }
        List<PlatformPropertyKey> platformPropertyKeyList = productFeignClient.getPlatformPropertyBySkuId(skuId);
        if(!CollectionUtils.isEmpty(platformPropertyKeyList)){
            List<SearchPlatformProperty> PlatformPropertyList = platformPropertyKeyList.stream().map((platformPropertyKey) -> {
                SearchPlatformProperty searchPlatformProperty = new SearchPlatformProperty();
                String platformPropertyValue = platformPropertyKey.getPropertyValueList().get(0).getPropertyValue();
                String propertyKey = platformPropertyKey.getPropertyKey();
                Long id = platformPropertyKey.getId();
                searchPlatformProperty.setPropertyKey(propertyKey);
                searchPlatformProperty.setPropertyKeyId(id);
                searchPlatformProperty.setPropertyValue(platformPropertyValue);
                return searchPlatformProperty;
            }).collect(Collectors.toList());
            product.setPlatformProperty(PlatformPropertyList);
        }
        //提交
        productRepository.save(product);
    }

    @Override
    public void offSale(Long skuId) {
        productRepository.deleteById(skuId);
    }

    @Override
    public void incrHotScore(Long skuId) {
        String hotKey = "hotScore";
        Double aDouble = redisTemplate.opsForZSet().incrementScore(hotKey, "skuId:" + skuId, 1);
        if (aDouble%5 == 0){
            Product product = productRepository.findById(skuId).get();
            product.setHotScore(Math.round(aDouble));
            productRepository.save(product);
        }
    }

    @SneakyThrows
    @Override
    public SearchResponseVo searchProduct(SearchParam searchParam) {
        //1.生成动态查询dsl语句
        SearchRequest searchRequest = this.buildQueryDsl(searchParam);
        //2.通过该语句实现查询
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        //3.将查询结果进行解析封装到一个对象当中
        SearchResponseVo searchResponseVo = this.parseSearchResult(searchResponse);
        //4.给searchResponseVo赋予默认分页信息
        searchResponseVo.setPageNo(searchParam.getPageNo());
        searchResponseVo.setPageSize(searchParam.getPageSize());
        //5.设置总页数
        Long totalPages = searchResponseVo.getTotal()/searchParam.getPageSize();
        if(searchResponseVo.getTotal()%searchParam.getPageSize()!=0){
            totalPages++;
        }
        searchResponseVo.setTotalPages(totalPages);
        return searchResponseVo;
    }

    private SearchResponseVo parseSearchResult(SearchResponse searchResponse) {
        SearchResponseVo searchResponseVo = new SearchResponseVo();
        SearchHits hits1st = searchResponse.getHits();
        SearchHit[] hits2nd = hits1st.getHits();
        List<Product> productList = new ArrayList<>();
        if (hits2nd!= null &&hits2nd.length>0){
            for (SearchHit documentFields : hits2nd) {
                Product product = JSONObject.parseObject(documentFields.getSourceAsString(), Product.class);
                if(documentFields.getHighlightFields().get("productName")!=null){
                    Text productName = documentFields.getHighlightFields().get("productName").getFragments()[0];
                    product.setProductName(productName.toString());
                }
                productList.add(product);
            }
        }
        searchResponseVo.setProductList(productList);
        searchResponseVo.setTotal(hits1st.totalHits);

        ParsedTerms brandIdAgg = searchResponse.getAggregations().get("brandIdAgg");
        List<SearchBrandVo> brandVoList = brandIdAgg.getBuckets().stream().map(bucket -> {
            SearchBrandVo searchBrandVo = new SearchBrandVo();
            searchBrandVo.setBrandId(bucket.getKeyAsNumber().longValue());
            ParsedTerms brandNameAgg = bucket.getAggregations().get("brandNameAgg");
            searchBrandVo.setBrandName(brandNameAgg.getBuckets().get(0).getKeyAsString());
            ParsedTerms brandLogoUrlAgg = bucket.getAggregations().get("brandLogoUrlAgg");
            searchBrandVo.setBrandLogoUrl(brandLogoUrlAgg.getBuckets().get(0).getKeyAsString());
            return searchBrandVo;
        }).collect(Collectors.toList());
        searchResponseVo.setBrandVoList(brandVoList);

        ParsedNested platformPropertyAgg = searchResponse.getAggregations().get("platformPropertyAgg");
        ParsedTerms propertyKeyIdAgg = platformPropertyAgg.getAggregations().get("propertyKeyIdAgg");
        List<? extends Terms.Bucket> buckets = propertyKeyIdAgg.getBuckets();
        if (!CollectionUtils.isEmpty(buckets)){
            List<SearchPlatformPropertyVo> platformPropertyList = buckets.stream().map(bucket -> {
                SearchPlatformPropertyVo searchPlatformProperty = new SearchPlatformPropertyVo();
                searchPlatformProperty.setPropertyKeyId(bucket.getKeyAsNumber().longValue());
                ParsedTerms propertyKeyAgg = bucket.getAggregations().get("propertyKeyAgg");
                searchPlatformProperty.setPropertyKey(propertyKeyAgg.getBuckets().get(0).getKeyAsString());
                ParsedTerms propertyValueAgg = bucket.getAggregations().get("propertyValueAgg");
                List<String> propertyValueList = propertyValueAgg.getBuckets().stream().map(Terms.Bucket::getKeyAsString).collect(Collectors.toList());
                searchPlatformProperty.setPropertyValueList(propertyValueList);
                return searchPlatformProperty;
            }).collect(Collectors.toList());
            searchResponseVo.setPlatformPropertyList(platformPropertyList);
        }

        return searchResponseVo;
    }

    private SearchRequest buildQueryDsl(SearchParam searchParam){

        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
            if(searchParam.getCategory1Id() != null){
                TermQueryBuilder category1Id = QueryBuilders.termQuery("category1Id", searchParam.getCategory1Id());
                boolQuery.filter(category1Id);
            }
            if(searchParam.getCategory2Id() != null){
                TermQueryBuilder category2Id = QueryBuilders.termQuery("category2Id", searchParam.getCategory2Id());
                boolQuery.filter(category2Id);
            }
            if(searchParam.getCategory3Id() != null){
                TermQueryBuilder category3Id = QueryBuilders.termQuery("category3Id", searchParam.getCategory3Id());
                boolQuery.filter(category3Id);
            }
            String brandName = searchParam.getBrandName();
            if(!StringUtils.isEmpty(brandName)){
                String[] split = brandName.split(":");
                if(split.length==2){
                    boolQuery.filter(QueryBuilders.termQuery("brandId",split[0]));
                }
            }

            String[] props = searchParam.getProps();
            if(props != null&&props.length>0){
                BoolQueryBuilder boolQueryPropertyValue= QueryBuilders.boolQuery();
                BoolQueryBuilder boolQueryPropertyValueChild= QueryBuilders.boolQuery();
                for (String prop : props) {
                    String[] split = prop.split(":");
                    if(split.length == 3){
                        TermQueryBuilder termPropertyId = QueryBuilders.termQuery("platformProperty.propertyKeyId", split[0]);
                        boolQueryPropertyValueChild.must(termPropertyId);
                        TermQueryBuilder termPropertyValue = QueryBuilders.termQuery("platformProperty.propertyValue", split[1]);
                        boolQueryPropertyValueChild.must(termPropertyValue);
                        boolQueryPropertyValue.must(QueryBuilders.nestedQuery("platformProperty",boolQueryPropertyValueChild, ScoreMode.None));
                    }
                }
                boolQuery.filter(boolQueryPropertyValue);
            }

            String keyword = searchParam.getKeyword();
            if(!StringUtils.isEmpty(keyword)){
                MatchQueryBuilder productName = QueryBuilders.matchQuery("productName", keyword).operator(Operator.AND);
                boolQuery.must(productName);
            }

            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
            searchSourceBuilder.query(boolQuery);
            searchSourceBuilder.from((searchParam.getPageNo()-1)*searchParam.getPageSize());
            searchSourceBuilder.size(searchParam.getPageSize());

            String order = searchParam.getOrder();
            if(!StringUtils.isEmpty(order)){
                String[] split = order.split(":");
                if(split.length==2){
                    String field = null;
                    switch (split[0]){
                        case "1":
                            field = "hotScore";
                            break;
                        case "2":
                            field = "price";
                            break;
                    }
                    searchSourceBuilder.sort(field,"asc".equals(split[1])? SortOrder.ASC:SortOrder.DESC);
                }else {
                    searchSourceBuilder.sort("hotScore",SortOrder.DESC);
                }

            }
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("productName");
            highlightBuilder.preTags("</span>");
            highlightBuilder.postTags("<span style=color:red>");
            searchSourceBuilder.highlighter(highlightBuilder);

            TermsAggregationBuilder brandAggsBuilder = AggregationBuilders.terms("brandIdAgg").field("brandId")
                    .subAggregation(AggregationBuilders.terms("brandNameAgg").field("brandName"))
                    .subAggregation(AggregationBuilders.terms("brandLogoUrlAgg").field("brandLogoUrl"));

            TermsAggregationBuilder propertyKeyIdAggsBuilder = AggregationBuilders.terms("propertyKeyIdAgg").field("platformProperty.propertyKeyId")
                    .subAggregation(AggregationBuilders.terms("propertyKeyAgg").field("platformProperty.propertyKey"))
                    .subAggregation(AggregationBuilders.terms("propertyValueAgg").field("platformProperty.propertyValue"));
        NestedAggregationBuilder nestedAggregationBuilder = AggregationBuilders.nested("platformPropertyAgg", "platformProperty").subAggregation(propertyKeyIdAggsBuilder);

        searchSourceBuilder.aggregation(brandAggsBuilder);
            searchSourceBuilder.aggregation(nestedAggregationBuilder);
            searchSourceBuilder.fetchSource(new String[]{"id", "defaultImage", "productName", "price"},null);
        SearchRequest searchRequest = new SearchRequest("product");
        searchRequest.types("info");
        searchRequest.source(searchSourceBuilder);
        System.out.println("DSL:"+searchSourceBuilder.toString());
        return searchRequest;
    }
}
